/*
 * 
 * 
 */

package steg.steki;

import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;
import java.util.zip.GZIPOutputStream;
import javax.swing.JComponent;
import javax.swing.SwingWorker;
import steg.sekret.Sekret;
import steg.sekret.UnSekret;

/**
 *
 * @author 
 */
public abstract class PlikSteg implements InfPlikSteg {

    protected static int MAX_PROGRESS_VALUE = 100;

    protected File plik;
    protected JComponent jcomp;
    protected String processMsg = "";
    protected String processNag;
    protected StegException stegEx;
    protected Sekret sekret;
    protected File fileOut;

    private double progres = 0;



// =============================== FUNKCJE KRYTYCZNE ===========================

    public void steg( File fo, Sekret s) throws StegException, IOException {

        fileOut = fo;
        sekret = s;

        // wyczyszczenie exceptionów
        stegEx = null;

        PropertyChangeListener[] pcl = null;
        boolean flag = false;

        try{
            pcl = jcomp.getPropertyChangeListeners();
            flag = true;

        }catch(Exception ex){
            flag = false;
        }
        // wchodzimy do tasku, albo powielamy proces, albo zwyczajnie odpalamy funkcje
        if(flag) {

            task = new Task(this);
            task.addPropertyChangeListener( pcl[0]);
            task.execute();
        }else
            stegProcess();



    }

    /**
     * Wyżuca zapamiętane wyjątki, które niestety nie są możliwe do wyłapania w żaden sposób
     * ze względu na użycie klasy SwingWorker.
     * @return
     */
    public StegException throwException() {
        return stegEx;
    }

    /**
     * Komponent z ChangeListenerem, pomocnym przy monitorowaniu procesów.
     * @param jc
     */
    public void addComponentWithChangeListener(JComponent jc) {
        jcomp = jc;
    }

    /**
     * Tekst o operacji steg() aktualnie przeprowadzanej.
     * @return
     */
    public String getProcessConsoleText() {
        String temp = processMsg;
        processMsg = ""; // czyszcze nie bufora
        return temp;
    }

    /**
     * Tekst nagłówkowy steg() aktualnie przeprowadzanej.
     * @return
     */
    public String getProcessTaskText() {
        return processNag;
    }


  // =============================== FUNKCJE DODATKOWE =========================

    /**
     * Dodawanie w procentach,
     * np. addProgressInPercent(0.1); posunie progres o 1 promil
     *
     * @param p
     */
    protected void addProgressInPercent(double p) {

        // 1 procent razy tyle ile procentow dodano
        progres += (double)(MAX_PROGRESS_VALUE/100) * p;



        if(progres>MAX_PROGRESS_VALUE)
            progres = MAX_PROGRESS_VALUE;
        this.task.firePropertyChange("progress", (int)progres-p, (int)progres);
    }


    /**
     * Przygotowuje Sekret przed właściwą steganografią, monitoruje zmiany.
     */
    protected void initSekret() throws StegException {

        //zaczynamy
        progres = 0;

        // tylko po to aby obliczyc % kompresji! podobny plik jest w stegProgress ale to nie ten!
        File tempSekretFile = sekret.getFile();


        // ustawianie parametrów
            // sciema ale chce aby zostały wyświetlone w konsoli
        processNag = "Krok  1/9: inicjalizacja procesu";
        processMsg += " ..start\n ..przygotowanie danych\n"; // dodajemy, a po pobraniu konsola wyczyści string
        addProgressInPercent(1);


        // czy bedzie haslo
        processNag = "Krok  2/9: inicjalizacja hasła pliku";
        if(sekret.isCzyHaslo())
            processMsg += " ..załadowanie hasła pliku [***]\n";
        else
            processMsg += " ..brak hasła\n";

        //aktualizacja
        addProgressInPercent(1);


        //ZIP owanie wiadomości
        processNag = "Krok  3/9: kompresja wiadomości";
        if(sekret.isZip())
            processMsg += " ..kompresja danych do formatu GZIP\n";
        else
            processMsg += " ..brak kompresji\n";

        //aktualizacja
        addProgressInPercent(1);

        if(sekret.isZip() && sekret.isSecretAsText()) {
            processMsg += " .. ...\n";
            try {
                //kompresja wiadomosci string
                sekret.setWiadomosc( sekret.getWiadomosc() );
            } catch (IOException ex) {

                throw new StegException(ex, StegException.BLAD_INNY, "Błąd kompresji pliku do formatu GZIP." );

            }


            processMsg += " ..skompresowano ["+ (float)(sekret.getWiadomoscBytes().length*100/sekret.getWiadomosc().length() ) +"%]\n";
        }


        // czy plik i czy zip
        if(sekret.isZip() && !sekret.isSecretAsText()) {
            processMsg += " ...\n";
            // no to leimy z gzip
            try {
                //generownie rozroznialnika, tj tagu z daty ktory rozrozni pliki
                // i podmiana, teraz sekretm jest co innego, NA SAMYM KONCU TRZEBA TO ODMIENIC !
                File temp = new File( sekret.getFile().getAbsolutePath()+".gzip" );
                sekret.setFile( temp );
                processMsg += " ..generowanie nowego pliku: "+sekret.getFile().getName()+"\n";

                // otwarcie strumienia GZIP
                GZIPOutputStream out = new GZIPOutputStream( new FileOutputStream(sekret.getFile() ));

                // otwarcie gzipowanego pliku
                FileInputStream in = new FileInputStream(plik);


                // ile to promili taki 1 cykl
                double proc = 50 / sekret.getFile().length();


                // wżucamy do strumienia
                byte[] buf = new byte[1024];
                int len;


                // tego nie zabardzo rozumiem, bo wcale to nie sczytuje bufora a 1 bajt,
                // wiec moze sie jebnac na innych systemach ! eh
                while ((len = in.read(buf)) > 0) {
                    out.write(buf, 0, len);

                    // postep
                    addProgressInPercent(proc);


                }
                in.close();
                out.finish();
                out.close();


            } catch (IOException ioex) {

                throw new StegException(ioex, StegException.BLAD_INNY, "Błąd kompresji pliku do formatu GZIP." );
            }



            processMsg += " ..skompresowano ["+ ( sekret.getFile().length()*100/tempSekretFile.length()) +"%\n";

        }else{
            addProgressInPercent(50);
        }


    }

  // =============================== FUNKCJE ABSTRACTY =========================
    protected abstract Void doInBackground();
    protected abstract void done();
    public abstract void stegProcess() throws StegException ;


// ------------------------------- FUNKCJE  POMOCNICZE CZY/SET -----------------
    public File getFile() {
        return plik;
    }

    public void setFile(File plik) {
        this.plik = plik;
    }

    public void setMaxProgressValue(int i) {
        MAX_PROGRESS_VALUE = i;

    }

    public int getMaxProgressValue() {
        return MAX_PROGRESS_VALUE ;

    }

// ------------------------------- PRYWATNA KLASA TASK -------------------------

    /**
     *
     * KLASA MUSI BYĆ, PONIEWAŻ execute() działa tylko raz, tak jest w przykładzie
     * z sun.com, nie wiem jak wymusić ponowny rozruch bez stworzenia nowej klasy.
     * Nowej klasy nie mogę zrobić, bo
     * a) nie o to chodzi
     * b) stracę wszystkie dane
     *
     * Co robić ? Ehh muszę umieścić tu to coś dziwnego, nie podoba mi się to
     * lepiej by było aby całość dziedziczyła po SwingWorker, no ale wtedy
     * problem jest z execute() :/
     *
     */
    private Task task;

    /**
     *
     */
    private class Task extends SwingWorker<Void, Void> {

        PlikSteg rodzic;

        public Task(PlikSteg ps) {
            rodzic = ps;
        }

        @Override
        public Void doInBackground() {

            rodzic.doInBackground();

            return null;
        }

        @Override
        public void done() {
            rodzic.done();
        }

        public void wydluzacz(int oile) {


    // ============== ZOSTAWIĆ DO TESTÓW

            Random random = new Random();
            int progress = 0;
            try {
                Thread.sleep(1000);
                while (progress < oile && !isCancelled()) {

                    Thread.sleep(random.nextInt(1000));

                    progress += random.nextInt(10);

                }
            } catch (InterruptedException ignore) {}


        }


    }

}
